home *** CD-ROM | disk | FTP | other *** search
/ Mac Magazin/MacEasy 14 / Mac Magazin and MacEasy Magazine CD - Issue 14.iso / Wissenschaft & Technik / Caveman Sound / CMSoundSystem.c < prev    next >
Text File  |  1995-09-11  |  40KB  |  1,317 lines

  1. /*****************************************************************************
  2.  * FILE:        CMSoundSystem.c
  3.  * AUTHOR:        David Hay
  4.  * CREATED:        March 9, 1995
  5.  * DESCRIPTION:    Routines for playing sound and music.
  6.  *
  7.  * Copyright © 1995 David Hay
  8.  *
  9.  * Permission to use, copy, and distribute this software and its documentation
  10.  * for any purpose is hereby granted without fee,  provided that (i) the above
  11.  * copyright notices  and  this permission notice  appear in all copies of the
  12.  * software  and  related documentation,  and (ii) the names of David Hay  and
  13.  * Caveman Creations may not be used in any advertising or publicity  relating
  14.  * to the software without the specific, prior written permission of David Hay
  15.  *
  16.  * THE SOFTWARE IS PROVIDED "AS-IS" AND WITHOUT WARRANTY OF ANY KIND, EXPRESS,
  17.  * IMPLIED  OR  OTHERWISE,  INCLUDING,  WITHOUT  LIMITATION,  ANY WARRANTY  OF
  18.  * MERCHANTABILITY  OR  FITNESS FOR  A PARTICULAR PURPOSE.  IN NO EVENT  SHALL
  19.  * DAVID HAY  OR  CAVEMAN CREATIONS  BE LIABLE  FOR  ANY SPECIAL,  INCIDENTAL,
  20.  * INDIRECT  OR  CONSEQUENTIAL DAMAGES OF ANY KIND,  OR ANY DAMAGES WHATSOEVER
  21.  * RESULTING FROM LOSS OF USE, DATA OR PROFITS,  WHETHER OR NOT ADVISED OF THE
  22.  * POSSIBILITY OF DAMAGE, AND ON ANY THEORY OF LIABILITY, ARISING OUT OF OR IN
  23.  * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  24.  *****************************************************************************/
  25.  
  26. #ifndef __GESTALT__
  27. #include <Gestalt.h>
  28. #endif
  29. #ifndef __SOUND__
  30. #include <Sound.h>
  31. #endif
  32. #ifndef __TRAPS__
  33. #include <Traps.h>
  34. #endif
  35. #ifndef __CMSOUNDSYSTEM__
  36. #include "CMSoundSystem.h"
  37. #endif
  38.  
  39.  
  40. #ifndef PUBLIC
  41. #define PUBLIC
  42. #endif
  43. #ifndef PRIVATE
  44. #define PRIVATE static
  45. #endif
  46.  
  47.  
  48. #define kSoundDone            99L            /* a sound is done playing */
  49. #define kMusicDone            100L        /* The music block is done playing */
  50. #define kSoundResourceDone    101L        /* a sound resource is done playing */
  51.  
  52. /*****************************************************************************
  53. ** ExtSndChannel    -- Describes an extended sound channel.
  54. **
  55. **        channel            - the Sound Manager channel data. NOTE: this field
  56. **                          MUST be first! This makes it possible for me to
  57. **                          simply cast the extended sound channel to a
  58. **                          sound channel for passing to Sound Manager routines.
  59. **
  60. **        soundPlaying    - The reference number of the sound currently playing
  61. **                          on this sound channel. If no sound is playing, this
  62. **                          is set to the value kNoSound. If a sound that is not
  63. **                          registered with the sound system is playing on the
  64. **                          sound channel, this has the value kSoundResource.
  65. **
  66. **        isValid            - Is the sound channel valid? If not a call to the
  67. **                          routine SndNewChannel() is needed to make the
  68. **                          channel valid again. Otherwise, the channel has
  69. **                          already been allocated and sound may be played on it.
  70. **
  71. **        soundData        - if the sound playing is an unregistered sound
  72. **                          resource, this is the sound resource playing on
  73. **                          the sound channel.
  74. **/
  75. struct ExtSndChannel
  76. {
  77.     SndChannel        channel;
  78.     short            soundPlaying;
  79.     Boolean            isValid;
  80.     SndListHandle    soundData;
  81. };
  82.  
  83. typedef struct ExtSndChannel ExtSndChannel;
  84. typedef ExtSndChannel *ExtSndChannelPtr;
  85.  
  86. /*****************************************************************************
  87. ** SoundSystem        -- Describes private data the sound system
  88. **
  89. **        numChannels        - the number of sound channels open
  90. **        soundChannel    - The list of available channels.
  91. **        numSounds        - the number of sound that have been registered with
  92. **                          the sound system.
  93. **        soundList        - A list of sound handles that have been registered.
  94. **        soundData        - Every time a sound handle is registered with the
  95. **                          sound system, the sound header is found and placed
  96. **                          in the corresponding slot in this list. This allows
  97. **                          the sound system to play the sounds more efficently
  98. **                          with bufferCmd's rather than using PlaySound().
  99. **        musicSequence    - describes which sounds to play and in what order
  100. **                          for a music sequence
  101. **        musicPosition    - the current position in the music sequence
  102. **
  103. **        smVersion        - the version of the sound manager used.
  104. **        smHasStereo        - is stereo sound is available?
  105. **        smHasStereoMixing
  106. **                        - can the stereo be mixed into a single sound channel?
  107. **        smHasDoubleBuffer
  108. **                        - Are double buffering routines available?
  109. **        smHasMultiChannels
  110. **                        - Are we allowed to have more than one channel open
  111. **                          at a time?
  112. **/
  113. struct SoundSystem
  114. {
  115.     short                numChannels;
  116.     ExtSndChannelPtr    soundChannel[kMaxChannels];    
  117.  
  118.     short                numSounds;
  119.     SndListHandle        soundList[kMaxSounds];
  120.     SoundHeaderPtr        soundData[kMaxSounds];
  121.     
  122.     PlaySequencePtr        musicSequence;
  123.     short                musicPosition;
  124.     
  125.     NumVersion            smVersion;
  126.     Boolean                smHasStereo         :1;
  127.     Boolean                smHasStereoMixing    :1;
  128.     Boolean                smHasDoubleBuffer    :1;
  129.     Boolean                smHasMultiChannels    :1;
  130. };
  131.  
  132. typedef struct SoundSystem SoundSystem;
  133.  
  134.  
  135. /************************ PRIVATE FUNCTION PROTOTYPES ************************/
  136.  
  137. PRIVATE OSErr PlayMusicBlock( short channelNum );
  138. PRIVATE pascal void SoundCallBack( SndChannelPtr theChannel, SndCommand theCmd );
  139. PRIVATE pascal void MusicCallBack( SndChannelPtr theChannel, SndCommand theCmd );
  140.  
  141.  
  142. /************************** PRIVATE GLOBAL VARIABLES *************************/
  143.  
  144. PRIVATE SndCallBackUPP soundCallBackUPP; /* callback when sounds are done */
  145. PRIVATE SoundSystem snd;                 /* sound system state information */
  146.  
  147.  
  148. /*===========================================================================*/
  149. /*                        PUBLIC FUNCTION DEFINITIONS                        */
  150. /*===========================================================================*/
  151.  
  152.  
  153. /*----------------------- INITIALIZATION/DEALLOCATION -----------------------*/
  154.  
  155.  
  156. /*****************************************************************************
  157.  * FUNCTION:    CMSInitSound
  158.  *
  159.  * INPUT:        short numChannels    -- Number of sound channels to allocate
  160.  * RETURNS:        OSErr                -- Returns any errors the occur.
  161.  *
  162.  * DESCRIPTION:    Initializes the sound system for use. It creates the requested
  163.  *                number of channels and initializes internal data. Any error
  164.  *                conditions are returned.
  165.  *****************************************************************************/
  166. PUBLIC OSErr CMSInitSound( short numChannels )
  167. {
  168.     ExtSndChannelPtr    aChannel;    /* A channel to initialize */
  169.     short                ii;            /* misc counter */
  170.     long                feature;    /* result value from Gestalt() */
  171.     OSErr                err;
  172.  
  173.         /*    Get some information about the current sound manager. First we
  174.         **    find out what version of the sound manager is available. To do
  175.         **    this we first have to check if the _SoundDispatch trap is
  176.         **    available. If so, then we can call SndSoundManagerVersion() to
  177.         **    get the version number. Otherwise, the enhanced sound manager
  178.         **    is not present so set the version number to 1.0
  179.         **/
  180.     if ( GetToolTrapAddress(_Unimplemented) != GetToolTrapAddress(_SoundDispatch) )
  181.     {
  182.         snd.smVersion = SndSoundManagerVersion();
  183.     }
  184.     else
  185.     {
  186.         snd.smVersion.majorRev = 1;
  187.         snd.smVersion.minorAndBugRev = 0;
  188.         snd.smVersion.stage = finalStage;
  189.         snd.smVersion.nonRelRev = 0;
  190.     }
  191.         
  192.         /*    Now that we have the version number, check for some capabilities
  193.         **    on the current system using gestalt. To check if double buffering
  194.         **    or multiple channels are available, we check two ways. If Sound
  195.         **    Manager 3.0 is available, we can simply look at the result
  196.         **    returned from Gestalt. Otherwise, we see if the ASC chip is
  197.         **    present to determine if the desired features are available.
  198.         **/
  199.     err = Gestalt( gestaltSoundAttr, &feature );
  200.     if ( err == noErr )
  201.     {
  202.         snd.smHasStereo = ((feature & (1L << gestaltStereoCapability)) != 0);
  203.         snd.smHasStereoMixing = ((feature & (1L << gestaltStereoMixing)) != 0);
  204.         if ( snd.smVersion.majorRev >= 3 )
  205.         {
  206.             snd.smHasDoubleBuffer =
  207.                         ((feature & (1L << gestaltSndPlayDoubleBuffer)) != 0);
  208.             snd.smHasMultiChannels =
  209.                         ((feature & (1L << gestaltMultiChannels)) != 0);
  210.         }
  211.         else
  212.         {
  213.             err = Gestalt( gestaltHardwareAttr, &feature );
  214.             if ( err == noErr )
  215.             {
  216.                 snd.smHasDoubleBuffer =
  217.                         ((feature & (1L << gestaltHasASC)) != 0);
  218.                 snd.smHasMultiChannels = snd.smHasDoubleBuffer;
  219.             }
  220.         }
  221.     }
  222.     
  223.         /*    Initialize the sound channel information so we know
  224.         **    what to dispose of if an error occurs.
  225.         **/
  226.     snd.numChannels = 0;
  227.     for ( ii = 0; ii < kMaxChannels; ii++ )
  228.         snd.soundChannel[ii] = NULL;
  229.     
  230.  
  231.         /*    Initialize the sound data information to indicate that
  232.         **    no sounds have been registered with the sound system
  233.         **/
  234.     snd.numSounds = 0;
  235.     for ( ii = 0; ii < kMaxSounds; ii++ )
  236.     {
  237.         snd.soundData[ii] = NULL;
  238.         snd.soundList[ii] = NULL;
  239.     }
  240.  
  241.         /*    Initialize the music information to indicate that no
  242.         **    music has been loaded
  243.         **/
  244.     snd.musicPosition = kNoSound;
  245.     snd.musicSequence = NULL;
  246.     
  247.  
  248.         /*    Setup the callback routine pointer for sounds. We will use the
  249.         **    same routine to handle sounds and music by putting a different
  250.         **    number in the callback command to determine what to do.
  251.         **/
  252.     soundCallBackUPP = NewSndCallBackProc( SoundCallBack );
  253.  
  254.  
  255.         /*    Allocate the sound channels. If more than one sound channel is
  256.         **    requested and multiple sound channels are not available, then
  257.         **
  258.         **/
  259.     for ( ii = 0; ii < numChannels; ii++ )
  260.     {
  261.         aChannel = (ExtSndChannelPtr) NewPtr( sizeof( ExtSndChannel ) );
  262.         if ( !aChannel )
  263.         {
  264.             err = MemError();
  265.             break;
  266.         }
  267.         else
  268.         {
  269.             aChannel->channel.qLength = stdQLength;
  270.             aChannel->channel.userInfo = ii;
  271.             aChannel->soundPlaying = kNoSound;
  272.             aChannel->isValid = false;
  273.             aChannel->soundData = NULL;
  274.             snd.soundChannel[ii] = aChannel;
  275.         }
  276.     }
  277.     if ( err == noErr )
  278.     {
  279.         snd.numChannels = numChannels;
  280.     }
  281.     else
  282.     {
  283.         CMSDisposeSound();
  284.     }
  285.         
  286.     return err;
  287. }
  288.  
  289. /*****************************************************************************
  290.  * FUNCTION:    CMSDisposeSound
  291.  *
  292.  * INPUT:        None.
  293.  * RETURNS:        Nothing.
  294.  *
  295.  * DESCRIPTION:    Stops all sounds and music and frees any memory allocated.
  296.  *****************************************************************************/
  297. PUBLIC void CMSDisposeSound( void )
  298. {
  299.     OSErr        err;
  300.     short        ii;
  301.  
  302.  
  303.         /*    Destroy all of the sound channels
  304.         **/
  305.     for ( ii = 0; ii < snd.numChannels; ii++ )
  306.     {
  307.         if ( snd.soundChannel[ii] )
  308.         {
  309.             CMSCloseChannel( ii );
  310.             DisposePtr( (Ptr) snd.soundChannel[ii] );
  311.             snd.soundChannel[ii] = NULL;
  312.         }
  313.     }
  314.     
  315.     DisposeRoutineDescriptor( soundCallBackUPP );
  316.     
  317.         /*    Dispose of the registered sound data
  318.         **/        
  319.     for ( ii = 0; ii < snd.numSounds; ii++ )
  320.     {
  321.         if ( snd.soundList[ii] )
  322.         {
  323.             HUnlock( (Handle) snd.soundList[ii] );
  324.             DisposeHandle( (Handle) snd.soundList[ii] );
  325.         }
  326.         snd.soundData[ii] = NULL;
  327.         snd.soundList[ii] = NULL;
  328.     }
  329.     
  330.         /*    Dispose of the music (if any)
  331.         **/
  332.     if ( snd.musicSequence )
  333.     {
  334.         DisposePtr( (Ptr) snd.musicSequence );
  335.         snd.musicSequence = NULL;
  336.     }
  337. }
  338.  
  339.  
  340.  
  341. /*---------------------------- CHANNEL MANAGEMENT ---------------------------*/
  342.  
  343.  
  344. /*****************************************************************************
  345.  * FUNCTION:    CMSCloseChannel
  346.  *
  347.  * INPUT:        short channelNum    -- the sound channel to close.
  348.  * RETURNS:        OSErr                -- result code.
  349.  *
  350.  * DESCRIPTION:    Stops the sound on the given channel and free's the channel.
  351.  *                Sound channels should be closed when an application receives
  352.  *                a suspend event. If the channel is already closed, nothing
  353.  *                happens and noErr is returned.
  354.  *****************************************************************************/
  355. PUBLIC OSErr CMSCloseChannel( short channelNum )
  356. {
  357.     OSErr    err;
  358.     
  359.     err = noErr;
  360.     if ( snd.soundChannel[channelNum]->isValid )
  361.     {
  362.         err = SndDisposeChannel( (SndChannelPtr) snd.soundChannel[channelNum],
  363.                                  true );
  364.         snd.soundChannel[channelNum]->isValid = false;
  365.     }
  366.     
  367.     return err;
  368. }
  369.  
  370.  
  371. /*****************************************************************************
  372.  * FUNCTION:    CMSCloseAllChannels
  373.  *
  374.  * INPUT:        None.
  375.  * RETURNS:        OSErr        -- result code.
  376.  *
  377.  * DESCRIPTION:    Closes all of the currently open channels.
  378.  *****************************************************************************/
  379. PUBLIC OSErr CMSCloseAllChannels( void )
  380. {
  381.     short    ii;
  382.     OSErr    err;
  383.     
  384.     err = noErr;
  385.     for ( ii = 0; ii < snd.numChannels; ii++ )
  386.         err = CMSCloseChannel( ii );
  387.         
  388.     return err;
  389. }
  390.     
  391.  
  392. /*****************************************************************************
  393.  * FUNCTION:    CMSOpenChannel
  394.  *
  395.  * INPUT:        short channelNum    -- the channel to open.
  396.  * RETURNS:        OSErr                -- result code.
  397.  *
  398.  * DESCRIPTION:    Opens the channel identified by the given channel number. If
  399.  *                the channel is already open, nothing is done. Sound may not be
  400.  *                played on a channel until it has been opened.
  401.  *****************************************************************************/
  402. PUBLIC OSErr CMSOpenChannel( short channelNum )
  403. {
  404.     OSErr    err;
  405.     
  406.     err = noErr;
  407.     if ( !snd.soundChannel[channelNum]->isValid )
  408.     {
  409.         err = SndNewChannel( (SndChannelPtr*) &snd.soundChannel[channelNum],
  410.                              sampledSynth, initNoInterp + initMono,
  411.                              soundCallBackUPP );
  412.         if ( err == noErr )
  413.             snd.soundChannel[channelNum]->isValid = true;
  414.     }
  415.     
  416.     return err;
  417. }
  418.  
  419.  
  420. /*****************************************************************************
  421.  * FUNCTION:    CMSOpenAllChannels
  422.  *
  423.  * INPUT:        None.
  424.  * RETURNS:        OSErr        -- result code
  425.  *
  426.  * DESCRIPTION:    Opens all of the channels that have been allocated.
  427.  *****************************************************************************/
  428. PUBLIC OSErr CMSOpenAllChannels( void )
  429. {
  430.     short    ii;
  431.     OSErr    err;
  432.     
  433.     err = noErr;
  434.     for ( ii = 0; ii < snd.numChannels; ii++ )
  435.         err = CMSOpenChannel( ii );
  436.         
  437.     return err;
  438. }
  439.  
  440.  
  441. /*****************************************************************************
  442.  * FUNCTION:    CMSStopSound
  443.  *
  444.  * INPUT:        short channelNum    -- the channel to stop sound on.
  445.  * RETURNS:        OSErr                -- result code.
  446.  *
  447.  * DESCRIPTION:    Stops any playing sound on the given sound channel.
  448.  *****************************************************************************/
  449. PUBLIC OSErr CMSStopSound( short channelNum )
  450. {
  451.     SndCommand            theCommand;
  452.     ExtSndChannelPtr    sndChannel;
  453.     OSErr                err;
  454.     
  455.     sndChannel = snd.soundChannel[channelNum];
  456.  
  457.         /*    Flush the sound channel of any other sound commands
  458.         **/
  459.     theCommand.cmd = flushCmd;
  460.     theCommand.param1 = 0;
  461.     theCommand.param2 = 0L;
  462.     err = SndDoImmediate( (SndChannelPtr) sndChannel, &theCommand );
  463.     
  464.         /*    Send a quiet command to stop any currently playing sounds
  465.         **/
  466.     if ( err == noErr )
  467.     {
  468.         theCommand.cmd = quietCmd;
  469.         theCommand.param1 = 0;
  470.         theCommand.param2 = 0L;
  471.         err = SndDoImmediate( (SndChannelPtr) sndChannel, &theCommand );
  472.     }
  473.     
  474.     sndChannel = snd.soundChannel[channelNum];
  475.     sndChannel->soundPlaying = kNoSound;
  476.     if ( sndChannel->soundData )
  477.     {
  478.         HUnlock( (Handle) sndChannel->soundData );
  479.         sndChannel->soundData = NULL;
  480.     }    
  481.  
  482.     return err;
  483. }
  484.  
  485.  
  486. /*****************************************************************************
  487.  * FUNCTION:    CMSIdleSoundTask
  488.  *
  489.  * INPUT:        None.
  490.  * RETURNS:        Nothing.
  491.  *
  492.  * DESCRIPTION:    Performs any idle tasks needed when a sound resource is being
  493.  *                played. It checks each of the sound channels to determine if
  494.  *                a sound is done playing and unlocks the sound resource if so.
  495.  *                This allows sound resources to be purged after they are done
  496.  *                playing.
  497.  *****************************************************************************/
  498. PUBLIC void CMSIdleSoundTask( void )
  499. {
  500.     short                ii;
  501.     ExtSndChannelPtr    sndChannel;
  502.     
  503.         /*    Loop over the available channels checking to see if one has
  504.         **    fallen silent. If so and there is a sound resource attached
  505.         **    to the channel, then unlock the sound so that it may be
  506.         **    purged. Also detach the sound resource from the channel so
  507.         **/
  508.     for ( ii = 0; ii < snd.numChannels; ii++ )
  509.     {
  510.         sndChannel = snd.soundChannel[ii];
  511.         if ( sndChannel->soundPlaying == kNoSound && sndChannel->soundData )
  512.         {
  513.             HUnlock( (Handle) sndChannel->soundData );
  514.             sndChannel->soundData = NULL;
  515.         }
  516.     }
  517. }
  518.  
  519.  
  520. /*****************************************************************************
  521.  * FUNCTION:    CMSGetSoundPlaying
  522.  *
  523.  * INPUT:        short channelNum    -- the channel to query.
  524.  * OUTPUT:        short* refNum        -- reference to the sound playing.
  525.  * RETURNS:        OSErr                -- result code.
  526.  *
  527.  * DESCRIPTION:    Returns the sound playing on the given channel in refNum. If
  528.  *                no sound is currently playing, kNoSound is returned as the
  529.  *                reference number. Any error conditions are returned.
  530.  *****************************************************************************/
  531. PUBLIC OSErr CMSGetSoundPlaying( short channelNum, short* refNum )
  532. {
  533.     if ( channelNum >= 0 && channelNum < snd.numChannels )
  534.     {
  535.         *refNum = snd.soundChannel[channelNum]->soundPlaying;
  536.         return noErr;
  537.     }
  538.     
  539.     return badChannel;
  540. }
  541.  
  542.  
  543. /*****************************************************************************
  544.  * FUNCTION:    CMSWaitForSilence
  545.  *
  546.  * INPUT:        short channelNum    -- the channel to wait on
  547.  * RETURNS:        OSErr                -- result code.
  548.  *
  549.  * DESCRIPTION:    Waits for the current sound to stop playing on the indicated
  550.  *                sound channel. If no sounds are playing on the channel,
  551.  *                the function returns immediately.
  552.  *****************************************************************************/
  553. PUBLIC OSErr CMSWaitForSilence( short channelNum )
  554. {
  555.     if ( channelNum >= 0 && channelNum < snd.numChannels )
  556.     {
  557.             /*    We turn off global optimizer to keep the compiler from putting
  558.             **    putting our loop control variable into a register. Since we
  559.             **    are waiting for an interrupt to occur, putting the LCV in a
  560.             **    register would put us in an infinite loop.  By disabling the
  561.             **  compiler's ability to assign variables to registers, we are
  562.             **    able to avoid this nasty problem.  I don't know if this hack
  563.             **    will work on the PPC since I don't have a PPC compiler.
  564.             **/
  565. #pragma options( !global_optimizer )
  566.  
  567.         while( snd.soundChannel[channelNum]->soundPlaying != kNoSound )
  568.         {}    /* Do nothing, just wait for the sound to complete */
  569.  
  570.         return noErr;
  571.     }
  572.     
  573.     return badChannel;    /* invalid channel specified */
  574. }
  575.  
  576.  
  577.  
  578. /*-------------------------- SOUND DATA MANAGEMENT --------------------------*/
  579.  
  580.  
  581. /*****************************************************************************
  582.  * FUNCTION:    CMSRegisterSound
  583.  *
  584.  * INPUT:        SndListHandle theSound    -- the sound to register
  585.  *                short* refNum            -- sound reference for later playback
  586.  * RETURNS:        OSErr                -- error code.
  587.  *
  588.  * DESCRIPTION:    Registers the given sound handle with the sound system. A
  589.  *                reference to the sound is returned in refNum which can be
  590.  *                used to play the sound at a later time.
  591.  *****************************************************************************/
  592. PUBLIC OSErr CMSRegisterSound( SndListHandle sndHandle, short* refNum )
  593. {
  594.     short            ii;
  595.     SoundHeaderPtr    sndHeader;
  596.     OSErr            err;
  597.     
  598.     err = noErr;
  599.     *refNum = kNoSound;
  600.     if ( sndHandle != NULL )
  601.     {
  602.             /*    First we get the sound header from the sound handle so
  603.             **    that the sound manager does not have to parse the sound
  604.             **    resource each time the sound is played.
  605.             **/
  606.         sndHeader = CMSGetSoundHeader( sndHandle );
  607.         
  608.             /*    Now we need to find a free slot in the list of available
  609.             **    sounds. Once we find a slot, insert the sound data into
  610.             **    that slot and return the slot number as the sound
  611.             **    reference.
  612.             **/
  613.         for ( ii = 0; ii < snd.numSounds; ii++ )
  614.         {
  615.             if ( snd.soundData[ii] == NULL )
  616.             {
  617.                 *refNum = ii;
  618.                 snd.soundData[ii] = sndHeader;
  619.                 snd.soundList[ii] = sndHandle;
  620.                 break;
  621.             }
  622.         }
  623.         
  624.             /*    There aren't enough slots, so bump the number of slots
  625.             **    if possible and insert the sound at the end of the sound
  626.             **    list. If there simply isn't any more room, return an error.
  627.             **/
  628.         if ( *refNum == kNoSound && snd.numSounds < kMaxSounds )
  629.         {
  630.             *refNum = snd.numSounds;
  631.             snd.soundData[snd.numSounds++] = sndHeader;
  632.         }
  633.         else
  634.         {
  635.             err = notEnoughBufferSpace;
  636.         }
  637.     }
  638.     
  639.     return err;
  640. }
  641.  
  642. /*****************************************************************************
  643.  * FUNCTION:    CMSRemoveSound
  644.  *
  645.  * INPUT:        short refNum        -- reference to the sound to remove
  646.  * RETURNS:        OSErr                -- error code.
  647.  *
  648.  * DESCRIPTION:    Removes the referenced sound from the sound system. If the
  649.  *                sound is currently playing on a sound channel, it is stopped
  650.  *                before the sound is disposed of.
  651.  *****************************************************************************/
  652. PUBLIC OSErr CMSRemoveSound( short refNum )
  653. {
  654.     short        ii;
  655.     SndCommand    theCommand;
  656.     OSErr        err;
  657.     
  658.     err = noErr;
  659.     if ( refNum >= 0 && refNum < snd.numSounds )
  660.     {
  661.             /*    Stop the sound if it is playing on one
  662.             **    of the sound channels
  663.             **/
  664.         for ( ii = 0; ii < snd.numChannels; ii++ )
  665.         {
  666.             if ( snd.soundChannel[ii]->soundPlaying == refNum )
  667.             {
  668.                 err = CMSStopSound( ii );
  669.             }
  670.             if ( err != noErr ) break;
  671.         }
  672.         
  673.             /*    Dispose of the sound handle and mark it's slot in the
  674.             **    list of registered sounds as now available.
  675.             **/
  676.         if ( err == noErr )
  677.         {        
  678.             HUnlock( (Handle) snd.soundList[refNum] );
  679.             DisposeHandle( (Handle) snd.soundList[refNum] );
  680.             snd.soundData[ii] = NULL;
  681.             snd.soundList[ii] = NULL;
  682.         }
  683.     }
  684.     
  685.     return err;
  686. }
  687.  
  688. /*****************************************************************************
  689.  * FUNCTION:    CMSLoadSound
  690.  *
  691.  * INPUT:        short soundID        -- resource ID of the sound to load.
  692.  *                short* refNum        -- sound reference for later playback
  693.  * RETURNS:        OSErr                -- error code.
  694.  *
  695.  * DESCRIPTION:    Loads the indicated sound from the current resource file. A
  696.  *                reference to the sound is returned in refNum which can be used
  697.  *                to play the sound back at a later time.
  698.  *****************************************************************************/
  699. PUBLIC OSErr CMSLoadSound( short soundID, short* refNum )
  700. {
  701.     Handle    sndHandle;
  702.     OSErr    err;
  703.     
  704.     err = noErr;
  705.     
  706.         /*    Get the sound resource and detach it from the resource file.
  707.         **/
  708.     sndHandle = GetResource( 'snd ', soundID );
  709.     if ( sndHandle == NULL )
  710.         err = ResError();
  711.     
  712.     if ( err == noErr )
  713.     {
  714.         DetachResource( (Handle) sndHandle );
  715.         err = ResError();
  716.     }
  717.     
  718.         /*    Now that we have the sound handle, register the sound
  719.         **    with the sound system.
  720.         **/
  721.     if ( err == noErr )
  722.         err = CMSRegisterSound( (SndListHandle) sndHandle, refNum );
  723.         
  724.     return err;
  725. }
  726.     
  727. /*****************************************************************************
  728.  * FUNCTION:    CMSPlaySound
  729.  *
  730.  * INPUT:        short refNum        -- reference to the sound to play.
  731.  *                short channelNum    -- the channel to play the sound on.
  732.  * RETURNS:        OSErr                -- error code.
  733.  *
  734.  * DESCRIPTION:    Plays the sound refered to by the given reference number on
  735.  *                the indicated sound channel. If the sound channel is not
  736.  *                already open, it is opened.
  737.  *****************************************************************************/
  738. PUBLIC OSErr CMSPlaySound( short refNum, short channelNum )
  739. {
  740.     SndCommand            theCommand;
  741.     ExtSndChannelPtr    theChannel;
  742.     OSErr                err;
  743.     
  744.     err = noErr;
  745.     if ( channelNum >= 0 && channelNum < snd.numChannels &&
  746.          refNum >= 0 && refNum < snd.numSounds )
  747.     {
  748.             /*    Open the sound channel if it is not already. Otherwise, stop
  749.             **    any sound that may be playing on that sound channel.
  750.             **/
  751.         if ( !snd.soundChannel[channelNum]->isValid )
  752.         {
  753.             err = CMSOpenChannel( channelNum );
  754.         }
  755.         else
  756.         {
  757.             err = CMSStopSound( channelNum );
  758.         }
  759.         theChannel = snd.soundChannel[channelNum];
  760.  
  761.             /*    Play the sound and install a completion callback routine
  762.             **/
  763.         if ( err == noErr )
  764.         {
  765.             theCommand.cmd = bufferCmd;
  766.             theCommand.param1 = 0;
  767.             theCommand.param2 = (long) snd.soundData[refNum];
  768.             err = SndDoImmediate( (SndChannelPtr) theChannel, &theCommand );
  769.         }
  770.                 
  771.         if ( err == noErr )
  772.         {
  773.             theCommand.cmd = callBackCmd;
  774.             theCommand.param1 = kSoundDone;
  775.             theCommand.param2 = SetCurrentA5();
  776.             err = SndDoCommand( (SndChannelPtr) theChannel, &theCommand, true );
  777.         }
  778.         
  779.         if ( err == noErr )
  780.             theChannel->soundPlaying = refNum;
  781.     }
  782.  
  783.     return err;
  784. }
  785.  
  786.  
  787. /*****************************************************************************
  788.  * FUNCTION:    CMSPlaySoundResource
  789.  *
  790.  * INPUT:        short resID            -- resource ID of the sound to play.
  791.  *                short channelNum    -- channel to play the sound on.
  792.  *                Boolean async        -- play the sound asychronously?
  793.  * RETURNS:        OSErr                -- result code.
  794.  *
  795.  * DESCRIPTION:    Loads the resource indicated and plays it on the appropriate
  796.  *                channel. Since the sound handle cannot be freed at callback
  797.  *                time. The routine CMSWaitForQuiet() should be used to wait
  798.  *                for the sound channel to become silent before calling
  799.  *                CMSStopSound() to free the sound resource.
  800.  *****************************************************************************/
  801. PUBLIC OSErr CMSPlaySoundResource( short resID, short channelNum, Boolean async )
  802. {
  803.     Handle        sndHandle;
  804.     SndCommand    theCommand;
  805.     OSErr        err;
  806.     
  807.     err = noErr;
  808.     
  809.     if ( channelNum >= 0 && channelNum < snd.numChannels )
  810.     {
  811.             /*    Open the sound channel if it is not already. Otherwise, stop
  812.             **    any sound that may be playing on that sound channel.
  813.             **/
  814.         if ( !snd.soundChannel[channelNum]->isValid )
  815.         {
  816.             err = CMSOpenChannel( channelNum );
  817.         }
  818.         else
  819.         {
  820.             err = CMSStopSound( channelNum );
  821.         }
  822.     
  823.             /*    Get the sound resource. Then lock it down and make it purgeable
  824.             **    so that it can be freed after the sound is finished playing.
  825.             **/
  826.         sndHandle = GetResource( 'snd ', resID );
  827.         if ( sndHandle == NULL )
  828.             return ResError();
  829.             
  830.         HLockHi( sndHandle );
  831.         HPurge( sndHandle );
  832.         
  833.             /*    Play the sound resource and install a callback routine to
  834.             **    handle the completion of the sound
  835.             **/
  836.         err = SndPlay( (SndChannelPtr) snd.soundChannel[channelNum],
  837.                        (SndListHandle) sndHandle, async );
  838.         if ( err == noErr )
  839.         {
  840.             theCommand.cmd = callBackCmd;
  841.             theCommand.param1 = kSoundResourceDone;
  842.             theCommand.param2 = SetCurrentA5();
  843.             err = SndDoCommand( (SndChannelPtr) snd.soundChannel[channelNum],
  844.                                 &theCommand, true );
  845.             
  846.             snd.soundChannel[channelNum]->soundData = (SndListHandle) sndHandle;
  847.             snd.soundChannel[channelNum]->soundPlaying = kSoundResource;
  848.         }
  849.     }    
  850.     return err;
  851. }
  852.  
  853.  
  854.  
  855. /*----------------------------- MUSIC MANAGEMENT ----------------------------*/
  856.  
  857.  
  858. /*****************************************************************************
  859.  * FUNCTION:    CMSLoadMusic
  860.  *
  861.  * INPUT:        short musicID        -- ID of the music sequence resource
  862.  * RETURNS:        OSErr                -- result code.
  863.  *
  864.  * DESCRIPTION:    Loads the music described by a MUSL resource. It loads each
  865.  *                of the sounds listed in the play sequence and registers them
  866.  *                with the sound system. It also sets up the internal play
  867.  *                sequence so that the music can later be played with a call
  868.  *                to CMSPlayMusic().
  869.  *****************************************************************************/
  870. PUBLIC OSErr CMSLoadMusic( short musicID )
  871. {
  872.     PlaySequenceHandle    sequenceH;
  873.     PlaySequencePtr        sequencePtr;
  874.     Size                sequenceSize;
  875.     short                ii, jj;
  876.     short                soundLoaded[kMaxSounds];
  877.     short                soundRef;
  878.     short                firstSound;
  879.     short                lastSound;
  880.     OSErr                err;
  881.     
  882.         /*    First we get the music list resource and copy it into
  883.         **    a newly allocated block of memory
  884.         **/
  885.     sequenceH = (PlaySequenceHandle) GetResource( kMusicResType, musicID );
  886.     if ( !sequenceH ) return ResError();
  887.  
  888.     sequenceSize = GetHandleSize( (Handle) sequenceH );
  889.     err = MemError();
  890.     
  891.     if ( err == noErr )
  892.     {
  893.         sequencePtr = (PlaySequencePtr) NewPtr( sequenceSize );
  894.         if ( sequencePtr == NULL )
  895.             err = MemError();
  896.     }
  897.     
  898.     if ( err == noErr && sequencePtr )
  899.     {
  900.         HLock( (Handle) sequenceH );
  901.         BlockMove( (Ptr)(*sequenceH),(Ptr)(sequencePtr), sequenceSize );
  902.         HUnlock( (Handle) sequenceH );
  903.     }
  904.     
  905.     ReleaseResource( (Handle) sequenceH );
  906.         
  907.     if ( err == noErr )
  908.     {
  909.             /*    Figure out the range of sound resources we are dealing with so
  910.             **    that we know which sounds to load in. It is assumed that the
  911.             **    sounds in the music sequence are contiguous and no other sounds
  912.             **    are involved.
  913.             **/
  914.         firstSound = sequencePtr->sequence[0];
  915.         lastSound = sequencePtr->sequence[0];
  916.         for ( ii = 1; ii < sequencePtr->sequenceLength; ii++ )
  917.         {
  918.             if ( sequencePtr->sequence[ii] < firstSound )
  919.                 firstSound = sequencePtr->sequence[ii];
  920.                 
  921.             if ( sequencePtr->sequence[ii] > lastSound )
  922.                 lastSound = sequencePtr->sequence[ii];
  923.         }
  924.     
  925.             /*    Load the sound data for the music. The sequence resource lists
  926.             **    the 'snd ' resources to play, so load each one in and update
  927.             **    the music sequence to refer to the new sound, rather than the
  928.             **    resource number.
  929.             **/    
  930.         for ( ii = firstSound; ii <= lastSound; ii++ )
  931.         {
  932.             err = CMSLoadSound( ii, &soundRef );
  933.             if ( err != noErr ) break;
  934.             
  935.             soundLoaded[ii - firstSound] = soundRef;
  936.             for ( jj = 0; jj < sequencePtr->sequenceLength; jj++ )
  937.             {
  938.                 if ( sequencePtr->sequence[jj] == ii )
  939.                     sequencePtr->sequence[jj] = soundRef;
  940.             }
  941.         }
  942.     }
  943.     
  944.         /*    Cleanup if there were any errors
  945.         **/
  946.     if ( err == noErr )
  947.     {
  948.         sequencePtr->loopStart--;
  949.         snd.musicSequence = sequencePtr;
  950.     }
  951.     else if ( sequencePtr )
  952.     {
  953.             /*    Dispose of the sounds we could load before the error occured.
  954.             **/
  955.         ii -= firstSound;
  956.         while ( --ii >= 0 )
  957.             CMSRemoveSound( soundLoaded[ii] );
  958.  
  959.         DisposePtr( (Ptr) sequencePtr );
  960.         snd.musicSequence = NULL;
  961.     }            
  962.     
  963.     return err;
  964. }
  965.  
  966.  
  967. /*****************************************************************************
  968.  * FUNCTION:    CMSRemoveMusic
  969.  *
  970.  * INPUT:        None.
  971.  * RETURNS:        OSErr        -- result code.
  972.  *
  973.  * DESCRIPTION:    Removes a previously loaded piece of music from the sound
  974.  *                system. All sounds associated with the music are removed as
  975.  *                is the play sequence.
  976.  *****************************************************************************/
  977. PUBLIC OSErr CMSRemoveMusic( void )
  978. {
  979.     PlaySequencePtr    sequencePtr;
  980.     short            ii;
  981.     OSErr            err;
  982.     
  983.     err = noErr;
  984.     sequencePtr = snd.musicSequence;
  985.     if ( sequencePtr )
  986.     {
  987.             /*    Loop through the entire play sequence and release each
  988.             **    sound. If the sound has already been released, skip it
  989.             **    and move on to the next one.
  990.             **/
  991.         for ( ii = 0; ii < sequencePtr->sequenceLength; ii++ )
  992.         {
  993.             if ( snd.soundData[sequencePtr->sequence[ii]] )
  994.                 CMSRemoveSound( sequencePtr->sequence[ii] );
  995.         }
  996.         
  997.         DisposePtr( (Ptr) sequencePtr );
  998.         snd.musicSequence = NULL;
  999.         snd.musicPosition = 0;
  1000.     }
  1001.     return err;
  1002. }
  1003.     
  1004.  
  1005. /*****************************************************************************
  1006.  * FUNCTION:    CMSPlayMusic
  1007.  *
  1008.  * INPUT:        short channelNum    -- the sound channel to play music on.
  1009.  * RETURNS:        OSErr                -- result code.
  1010.  *
  1011.  * DESCRIPTION:    Plays the music previously loaded by CMSLoadMusic. The music
  1012.  *                is played on the referenced sound channel.
  1013.  *****************************************************************************/
  1014. PUBLIC OSErr CMSPlayMusic( short channelNum )
  1015. {
  1016.     short    firstSound;
  1017.     short    ii;
  1018.     OSErr    err;
  1019.     
  1020.     err = noErr;
  1021.     if ( snd.musicSequence != NULL )
  1022.     {
  1023.             /*    If the sound channel is not already open, then open it.
  1024.             **    Otherwise, stop any sound that was previously playing on it.
  1025.             **/
  1026.         if ( !snd.soundChannel[channelNum]->isValid )
  1027.             err = CMSOpenChannel( channelNum );
  1028.         else
  1029.             err = CMSStopSound( channelNum );
  1030.  
  1031.             /*    Reset the music position and then play the first
  1032.             **    block of music.
  1033.             **/
  1034.         if ( err == noErr )
  1035.         {
  1036.             snd.musicPosition = 0;
  1037.             err = PlayMusicBlock( channelNum );
  1038.         }
  1039.     }
  1040.  
  1041.     return err;
  1042. }
  1043.  
  1044.  
  1045.  
  1046. /*----------------------------- UTILITY ROUTINES ----------------------------*/
  1047.  
  1048.  
  1049. /*****************************************************************************
  1050.  * FUNCTION:    CMSGetSoundHeader
  1051.  *
  1052.  * INPUT:        SndListHandle sndHandle    -- the sound handle to extract from
  1053.  * RETURNS:        SoundHeaderPtr            -- the header in the sound resource.
  1054.  *
  1055.  * DESCRIPTION:    Obtains a pointer to the sound header in the given sound
  1056.  *                resource. Because it returns a pointer into the handle, the
  1057.  *                handle is locked and should not be unlocked until the sound
  1058.  *                header is no longer needed. If there is not a sound header
  1059.  *                in the given resource, then NULL is returned. Otherwise a
  1060.  *                valid sound header is returned. It returns a pointer to a
  1061.  *                sampled sound header even if the sound header is actually an
  1062.  *                extended sound header or a compressed sound header.
  1063.  *****************************************************************************/
  1064. PUBLIC SoundHeaderPtr CMSGetSoundHeader( SndListHandle sndHandle )
  1065. {
  1066.     long    offset;        /* offset to sound header */
  1067.     OSErr    err;
  1068.  
  1069.     HLockHi( (Handle) sndHandle );
  1070.     
  1071.         /*    compute offset to sound header and use that offset to
  1072.         **    return a pointer into the sound handle.
  1073.         **/
  1074.     err = CMSGetSoundHeaderOffset( sndHandle, &offset );
  1075.     if ( err != noErr )                /* no sound header in resource */
  1076.         return NULL;
  1077.     else                            /* compute address of sound header */
  1078.         return ((SoundHeaderPtr)((Ptr)(*sndHandle) + offset));
  1079. }
  1080.  
  1081.  
  1082. /*****************************************************************************
  1083.  * FUNCTION:    CMSGetSoundHeaderOffset
  1084.  *
  1085.  * INPUT:        SndListHandle sndHandle    -- the sound handle to extract from
  1086.  * OUTPUT:        long* theOffset            -- offset to the sound header.
  1087.  * RETURNS:        OSErr                    -- result code.
  1088.  *
  1089.  * DESCRIPTION:    Traverses a sound resource until it reaches the sound data.
  1090.  *                It returns, in the offset parameter, the offset in bytes from
  1091.  *                the beginning of a sound resource to the sound header.
  1092.  *****************************************************************************/
  1093. PUBLIC OSErr CMSGetSoundHeaderOffset( SndListHandle soundHandle,
  1094.                                      long* theOffset )
  1095. {
  1096.     Ptr        sndPtr;            /* to navigate resource    */
  1097.     long    offset;            /* offset into resource    */
  1098.     short    numSynths;        /* info about resource    */
  1099.     short    numCmds;        /* info about resource    */
  1100.     Boolean    isDone;            /* are we done yet?        */
  1101.     OSErr    err;
  1102.  
  1103.         /*    If we have Sound Manager 3.0 or greater, then let it do
  1104.         **    the work for us. Otherwise, we have to parse the sound
  1105.         **    resource ourselves.
  1106.         **/
  1107.     if ( snd.smVersion.majorRev >= 3 )
  1108.         return GetSoundHeaderOffset( soundHandle, theOffset );
  1109.  
  1110.         /*    Initialize variables.
  1111.         **/
  1112.     offset = 0L;                    /* return 0 if no sound header found */
  1113.     sndPtr = (Ptr)*soundHandle;        /* point to start of resource data */
  1114.     isDone = false;                    /* haven't yet found sound header */
  1115.     err = noErr;
  1116.  
  1117.         /*    Skip everything before sound commands.
  1118.         **/
  1119.     switch ( ((SndListPtr) sndPtr)->format )
  1120.     {
  1121.         case firstSoundFormat:                /* format 1 'snd ' resource */
  1122.             numSynths = ((SndListPtr) sndPtr)->numModifiers;
  1123.             sndPtr += 2 * sizeof( short ) + numSynths * sizeof( ModRef );
  1124.             break;
  1125.  
  1126.         secondSoundFormat:                    /* format 2 'snd ' resource */
  1127.             sndPtr += 2 * sizeof( short );
  1128.             break;
  1129.             
  1130.         default:                            /* unrecognized resource format */
  1131.             err = badFormat;
  1132.             isDone = true;
  1133.             break;
  1134.     }
  1135.  
  1136.         /*    Find number of commands and move to start of first command.
  1137.         **/
  1138.     numCmds = *(short*)sndPtr;
  1139.     sndPtr += sizeof( short );
  1140.  
  1141.         /*    Search for bufferCmd or soundCmd to obtain sound header.
  1142.         **/
  1143.     while ( numCmds >= 1 && !isDone )
  1144.     {
  1145.         if ( (*(short*)sndPtr) == bufferCmd + dataOffsetFlag  ||
  1146.              (*(short*)sndPtr) == soundCmd + dataOffsetFlag )
  1147.         {
  1148.                 /*    bufferCmd or soundCmd found, copy offset
  1149.                 **    from sound command
  1150.                 **/
  1151.             offset = ((SndCommand*)sndPtr)->param2;
  1152.             isDone = true;                        /* get out of loop */
  1153.         }
  1154.         else
  1155.         {
  1156.                 /*    soundCmd or bufferCmd not found move to next command
  1157.                 **/
  1158.             sndPtr += sizeof( SndCommand );
  1159.             --numCmds;
  1160.         }
  1161.     }
  1162.  
  1163.     *theOffset = offset;        /* return offset */
  1164.     return err;                    /* return result code */
  1165. }
  1166.  
  1167.  
  1168.  
  1169. /*----------------------------- INQUIRY ROUTINES ----------------------------*/
  1170.  
  1171.  
  1172. /*****************************************************************************
  1173.  * FUNCTION:    CMSHasNewSoundManager
  1174.  *
  1175.  * INPUT:        None
  1176.  * RETURNS:        Boolean            -- is the new sound manager present?
  1177.  *
  1178.  * DESCRIPTION:    Determines if the current sound manager is the new one and
  1179.  *                returns true if present.
  1180.  *****************************************************************************/
  1181. PUBLIC Boolean CMSHasNewSoundManager( void )
  1182. {
  1183.     NumVersion    sndVersion;
  1184.     
  1185.     if ( GetToolTrapAddress(_Unimplemented) != GetToolTrapAddress(_SoundDispatch) )
  1186.     {
  1187.         sndVersion = SndSoundManagerVersion();
  1188.         return (sndVersion.majorRev >= 2);
  1189.     }
  1190.     
  1191.     return false;
  1192. }
  1193.  
  1194. /*****************************************************************************
  1195.  * FUNCTION:    CMSHasMultipleChannels
  1196.  *
  1197.  * INPUT:        None
  1198.  * RETURNS:        Boolean            -- Are more than one channel supported?
  1199.  *
  1200.  * DESCRIPTION:    Determines if multiple channels are availble.
  1201.  *****************************************************************************/
  1202. PUBLIC Boolean CMSHasMultipleChannels( void )
  1203. {
  1204.     OSErr    err;
  1205.     long    feature;
  1206.     
  1207.     err = Gestalt( gestaltSoundAttr, &feature );
  1208.     if ( err == noErr )
  1209.     {
  1210.         if ( CMSHasNewSoundManager() && SndSoundManagerVersion().majorRev >= 3 )
  1211.         {
  1212.             return ((feature & (1L << gestaltMultiChannels)) != 0);
  1213.         }
  1214.         else
  1215.         {
  1216.             err = Gestalt( gestaltHardwareAttr, &feature );
  1217.             if ( err == noErr )
  1218.                 return ((feature & (1L << gestaltHasASC)) != 0);
  1219.         }
  1220.     }
  1221.     return false;
  1222. }
  1223.  
  1224.  
  1225. /*===========================================================================*/
  1226. /*                       PRIVATE FUNCTION DEFINITIONS                        */
  1227. /*===========================================================================*/
  1228.  
  1229.  
  1230. /*****************************************************************************
  1231.  * FUNCTION:    PlayMusicBlock
  1232.  *
  1233.  * INPUT:        short channelNum    -- the channel to play the block on.
  1234.  * RETURNS:        OSErr                -- result code.
  1235.  *
  1236.  * DESCRIPTION:    Plays the next music block on the given channel.
  1237.  *****************************************************************************/
  1238. PRIVATE OSErr PlayMusicBlock( short channelNum )
  1239. {
  1240.     SndCommand    theCommand;
  1241.     short        soundRef;
  1242.     OSErr        err;
  1243.     
  1244.     if ( snd.musicPosition < 0 )
  1245.         return noErr;
  1246.         
  1247.     soundRef = snd.musicSequence->sequence[snd.musicPosition];
  1248.     if ( soundRef < 0 )
  1249.         return noErr;
  1250.         
  1251.     theCommand.cmd = bufferCmd;
  1252.     theCommand.param1 = 0;
  1253.     theCommand.param2 = (long) snd.soundData[soundRef];
  1254.     err = SndDoImmediate( (SndChannelPtr) snd.soundChannel[channelNum],
  1255.                           &theCommand );
  1256.         
  1257.     theCommand.cmd = callBackCmd;
  1258.     theCommand.param1 = kMusicDone;
  1259.     theCommand.param2 = SetCurrentA5();
  1260.     err = SndDoCommand( (SndChannelPtr) snd.soundChannel[channelNum],
  1261.                         &theCommand, false );
  1262.  
  1263.     snd.soundChannel[channelNum]->soundPlaying = soundRef;
  1264.  
  1265.     return err;
  1266. }
  1267.         
  1268. /*****************************************************************************
  1269.  * FUNCTION:    SoundCallBack
  1270.  *
  1271.  * INPUT:        SndChannelPtr theChannel    -- the current sound channel.
  1272.  *                SndCommand theCmd            -- the command that caused this.
  1273.  * RETURNS:        Nothing.
  1274.  *
  1275.  * DESCRIPTION:    This is the sound callback for the sound system. It has
  1276.  *                different behaivor depending on the contents of the command.
  1277.  *****************************************************************************/
  1278. PRIVATE pascal void SoundCallBack( SndChannelPtr theChannel, SndCommand theCmd )
  1279. {
  1280.     long    myA5;
  1281.     short    channelNum;
  1282.     
  1283.     myA5 = SetA5( theCmd.param2 );
  1284.  
  1285.     switch( theCmd.param1 )
  1286.     {
  1287.         case kSoundDone:
  1288.         case kSoundResourceDone:
  1289.                 /*    A sound has finished playing on the sound channel so
  1290.                 **    mark the sound channel as no longer playing any sound
  1291.                 **    and return.
  1292.                 **/
  1293.             channelNum = theChannel->userInfo;
  1294.             snd.soundChannel[channelNum]->soundPlaying = kNoSound;
  1295.             break;
  1296.             
  1297.             
  1298.             
  1299.         case kMusicDone:
  1300.                 /*    Advance the music position counter. If we have reached
  1301.                 **    the end of the music, loop back to the appropriate place
  1302.                 **    in the sequence.
  1303.                 **/
  1304.             if ( (++snd.musicPosition) >= snd.musicSequence->sequenceLength )
  1305.                 snd.musicPosition = snd.musicSequence->loopStart;
  1306.                 
  1307.                 /*    Extract the channel number and play the next music
  1308.                 **    block on that channel.
  1309.                 **/
  1310.             channelNum = theChannel->userInfo;    
  1311.             PlayMusicBlock( channelNum );
  1312.             break;
  1313.     }
  1314.  
  1315.     myA5 = SetA5( myA5 );
  1316. }
  1317.